import { Class } from '../../core/Class';
import { setOptions } from '../../core/Util';
import { toPoint as point } from '../../geometry/Point';
import Browser from '../../core/Browser';

/*
 * @class Icon
 * @aka L.Icon
 *
 * Represents an icon to provide when creating a marker.
 *
 * @example
 *
 * ```js
 * var myIcon = L.icon({
 *     iconUrl: 'my-icon.png',
 *     iconRetinaUrl: 'my-icon@2x.png',
 *     iconSize: [38, 95],
 *     iconAnchor: [22, 94],
 *     popupAnchor: [-3, -76],
 *     shadowUrl: 'my-icon-shadow.png',
 *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
 *     shadowSize: [68, 95],
 *     shadowAnchor: [22, 94]
 * });
 *
 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
 * ```
 *
 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
 *
 */

export var Icon = Class.extend({
  /* @section
   * @aka Icon options
   *
   * @option iconUrl: String = null
   * **(required)** The URL to the icon image (absolute or relative to your script path).
   *
   * @option iconRetinaUrl: String = null
   * The URL to a retina sized version of the icon image (absolute or relative to your
   * script path). Used for Retina screen devices.
   *
   * @option iconSize: Point = null
   * Size of the icon image in pixels.
   *
   * @option iconAnchor: Point = null
   * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
   * will be aligned so that this point is at the marker's geographical location. Centered
   * by default if size is specified, also can be set in CSS with negative margins.
   *
   * @option popupAnchor: Point = [0, 0]
   * The coordinates of the point from which popups will "open", relative to the icon anchor.
   *
   * @option tooltipAnchor: Point = [0, 0]
   * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
   *
   * @option shadowUrl: String = null
   * The URL to the icon shadow image. If not specified, no shadow image will be created.
   *
   * @option shadowRetinaUrl: String = null
   *
   * @option shadowSize: Point = null
   * Size of the shadow image in pixels.
   *
   * @option shadowAnchor: Point = null
   * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
   * as iconAnchor if not specified).
   *
   * @option className: String = ''
   * A custom class name to assign to both icon and shadow images. Empty by default.
   */

  options: {
    popupAnchor: [0, 0],
    tooltipAnchor: [0, 0],

    // @option crossOrigin: Boolean|String = false
    // Whether the crossOrigin attribute will be added to the tiles.
    // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
    // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
    crossOrigin: false,
  },

  initialize: function (options) {
    setOptions(this, options);
  },

  // @method createIcon(oldIcon?: HTMLElement): HTMLElement
  // Called internally when the icon has to be shown, returns a `<img>` HTML element
  // styled according to the options.
  createIcon: function (oldIcon) {
    return this._createIcon('icon', oldIcon);
  },

  // @method createShadow(oldIcon?: HTMLElement): HTMLElement
  // As `createIcon`, but for the shadow beneath it.
  createShadow: function (oldIcon) {
    return this._createIcon('shadow', oldIcon);
  },

  _createIcon: function (name, oldIcon) {
    var src = this._getIconUrl(name);

    if (!src) {
      if (name === 'icon') {
        throw new Error('iconUrl not set in Icon options (see the docs).');
      }
      return null;
    }

    var img = this._createImg(
      src,
      oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null,
    );
    this._setIconStyles(img, name);

    if (this.options.crossOrigin || this.options.crossOrigin === '') {
      img.crossOrigin =
        this.options.crossOrigin === true ? '' : this.options.crossOrigin;
    }

    return img;
  },

  _setIconStyles: function (img, name) {
    var options = this.options;
    var sizeOption = options[name + 'Size'];

    if (typeof sizeOption === 'number') {
      sizeOption = [sizeOption, sizeOption];
    }

    var size = point(sizeOption),
      anchor = point(
        (name === 'shadow' && options.shadowAnchor) ||
          options.iconAnchor ||
          (size && size.divideBy(2, true)),
      );

    img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');

    if (anchor) {
      img.style.marginLeft = -anchor.x + 'px';
      img.style.marginTop = -anchor.y + 'px';
    }

    if (size) {
      img.style.width = size.x + 'px';
      img.style.height = size.y + 'px';
    }
  },

  _createImg: function (src, el) {
    el = el || document.createElement('img');
    el.src = src;
    return el;
  },

  _getIconUrl: function (name) {
    return (
      (Browser.retina && this.options[name + 'RetinaUrl']) ||
      this.options[name + 'Url']
    );
  },
});

// @factory L.icon(options: Icon options)
// Creates an icon instance with the given options.
export function icon(options) {
  return new Icon(options);
}
